﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.Util;
using UnityEngine.Serialization;

namespace UnityEngine.ResourceManagement.ResourceProviders
{
    class AssetBundleResource : IAssetBundleResource
    {
        AssetBundle m_AssetBundle;
        //DownloadHandlerAssetBundle m_downloadHandler;
        DownloadHandler m_downloadHandler;
        AsyncOperation m_RequestOperation;
        SWebRequestQueueOperation m_SWebRequestQueueOperation;
        internal ProvideHandle m_ProvideHandle;
        internal AssetBundleRequestOptions m_Options;
        int m_Retries;
        long m_BytesToDownload;

        internal UnityWebRequest CreateWebRequest(IResourceLocation loc)
        {
            //Debug.LogError("sss:" + loc.InternalId);
            //var url = m_ProvideHandle.ResourceManager.TransformInternalId(loc);
            var url = InternalIdTransformFunc(loc);
            if (m_Options == null)
                return UnityWebRequest.Get(url);
            UnityWebRequest webRequest;
            //if (!string.IsNullOrEmpty(m_Options.Hash))
            {
                //CachedAssetBundle cachedBundle = new CachedAssetBundle(m_Options.BundleName, Hash128.Parse("sssseee"));
                //CachedAssetBundle cachedBundle = new CachedAssetBundle("11/cc.ab", Hash128.Parse(m_Options.Hash));
                CachedAssetBundle cachedBundle = new CachedAssetBundle("11/cc.ab", Hash128.Parse("c8edd68d164190016af684b4af2b3419"));
                //Debug.LogError("bName:" + m_Options.Hash);

                //webRequest = UnityWebRequestAssetBundle.GetAssetBundle(url, cachedBundle);
                webRequest = UnityWebRequest.Get(url);

            }

            //string writePath = webRequest.url.Replace("http://10.40.0.209/Test/", "D://Test");
            //UpdateDownloadHandler handle = new UpdateDownloadHandler(writePath);
            //webRequest.downloadHandler = handle;

            if (m_Options.Timeout > 0)
                webRequest.timeout = m_Options.Timeout;
            if (m_Options.RedirectLimit > 0)
                webRequest.redirectLimit = m_Options.RedirectLimit;
#if !UNITY_2019_3_OR_NEWER
            webRequest.chunkedTransfer = m_Options.ChunkedTransfer;
#endif
            if (m_ProvideHandle.ResourceManager.CertificateHandlerInstance != null)
            {
                webRequest.certificateHandler = m_ProvideHandle.ResourceManager.CertificateHandlerInstance;
                webRequest.disposeCertificateHandlerOnDispose = false;
            }


            return webRequest;
        }

        float PercentComplete() { return m_RequestOperation != null ? m_RequestOperation.progress : 0.0f; }

        DownloadStatus GetDownloadStatus()
        {
            if (m_Options == null)
                return default;
            var status = new DownloadStatus() { TotalBytes = m_BytesToDownload, IsDone = PercentComplete() >= 1f };
            if (m_BytesToDownload > 0)
            {
                if (m_SWebRequestQueueOperation != null)
                    status.DownloadedBytes = (long)(m_SWebRequestQueueOperation.m_WebRequest.downloadedBytes);
                else if (PercentComplete() >= 1.0f)
                    status.DownloadedBytes = status.TotalBytes;
            }
            return status;
        }

        /// <summary>
        /// Get the asset bundle object managed by this resource.  This call may force the bundle to load if not already loaded.
        /// </summary>
        /// <returns>The asset bundle.</returns>
        public AssetBundle GetAssetBundle()
        {
            if (m_AssetBundle == null && m_downloadHandler != null)
            {
                m_AssetBundle =  AssetBundle.LoadFromMemory(m_downloadHandler.data);
                m_downloadHandler.Dispose();
                m_downloadHandler = null;
            }
            return m_AssetBundle;
        }

        internal void Start(ProvideHandle provideHandle)
        {
            m_Retries = 0;
            m_AssetBundle = null;
            m_downloadHandler = null;
            m_RequestOperation = null;
            m_ProvideHandle = provideHandle;
            m_Options = m_ProvideHandle.Location.Data as AssetBundleRequestOptions;
            if (m_Options != null)
                m_BytesToDownload = m_Options.ComputeSize(m_ProvideHandle.Location, m_ProvideHandle.ResourceManager);
            provideHandle.SetProgressCallback(PercentComplete);
            provideHandle.SetDownloadProgressCallbacks(GetDownloadStatus);
            BeginOperation();
        }

        //=======================================================================================================================================================
        /// <summary>路径重定向</summary>
        /// <param name="location"></param>
        /// <returns></returns>
        private string InternalIdTransformFunc(UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation location)
        {
            //return location.InternalId;
            //判定是否是一个AB包的请求
            if (location.Data is AssetBundleRequestOptions)
            {
                var path = GStringBuilder.Contact(Application.dataPath, "/../ServerData/", Platform.name, "/", location.PrimaryKey);

                //if (FileManager.StreamingExists(path))
                //{
                //UnityEngine.Debug.LogError(location.InternalId);
                //return path;
                return string.Concat(SDynamicLocator.RootPath, location.PrimaryKey);
                //return string.Concat(Application.streamingAssetsPath,"/", location.PrimaryKey);
                //}
            }

            //Debug.LogError(location.InternalId);
            return location.InternalId;
        }

        private void BeginOperation()
        {
            //string path = m_ProvideHandle.ResourceManager.TransformInternalId(m_ProvideHandle.Location);
            string path = InternalIdTransformFunc(m_ProvideHandle.Location);
            if (File.Exists(path) || (Application.platform == RuntimePlatform.Android && path.StartsWith("jar:")))
            {
                m_RequestOperation = AssetBundle.LoadFromFileAsync(path, m_Options == null ? 0 : m_Options.Crc);
                m_RequestOperation.completed += LocalRequestOperationCompleted;
            }
            else if (ResourceManagerConfig.ShouldPathUseWebRequest(path))
            {
                var req = CreateWebRequest(m_ProvideHandle.Location);
                req.disposeDownloadHandlerOnDispose = false;
                m_SWebRequestQueueOperation = SWebRequestQueue.QueueRequest(req); 
                if (m_SWebRequestQueueOperation.IsDone)
                {
                    m_RequestOperation = m_SWebRequestQueueOperation.Result;
                    m_RequestOperation.completed += WebRequestOperationCompleted;
                }
                else
                {
                    m_SWebRequestQueueOperation.OnComplete += asyncOp =>
                    {
                        m_RequestOperation = asyncOp;
                        m_RequestOperation.completed += WebRequestOperationCompleted;
                    };
                }
            }
            else
            {
                m_RequestOperation = null;
                m_ProvideHandle.Complete<AssetBundleResource>(null, false, new Exception(string.Format("Invalid path in AssetBundleProvider: '{0}'.", path)));
            }
        }

        private void LocalRequestOperationCompleted(AsyncOperation op)
        {
            m_AssetBundle = (op as AssetBundleCreateRequest).assetBundle;
            m_ProvideHandle.Complete(this, m_AssetBundle != null, null);
        }

        private void WebRequestOperationCompleted(AsyncOperation op)
        {
            UnityWebRequestAsyncOperation remoteReq = op as UnityWebRequestAsyncOperation;
            var webReq = remoteReq.webRequest;
            if (string.IsNullOrEmpty(webReq.error))
            {
                m_downloadHandler = webReq.downloadHandler;

                string perPath = GStringBuilder.Contact("D:/2", "/");
                string writePath = webReq.url.Replace(SDynamicLocator.RootPath, perPath);

                string writeDir = Path.GetDirectoryName(writePath);
                //Debug.LogError(writeDir);
                if (!Directory.Exists(writeDir)) Directory.CreateDirectory(writeDir);
                byte[] data = m_downloadHandler.data;
                File.WriteAllBytes(writePath, data);

                //AssetBundle bundle = AssetBundle.LoadFromMemory(data);
                //string[] scenes = bundle.GetAllAssetNames();
                //for (int i = 0; i < scenes.Length; i++)
                //    Debug.LogError(scenes[i]);


                m_ProvideHandle.Complete(this, true, null);
            }
            else
            {
                m_downloadHandler = webReq.downloadHandler as DownloadHandlerAssetBundle;
                if(m_downloadHandler != null)
                    m_downloadHandler.Dispose();
                m_downloadHandler = null;
               // File.WriteAllBytes(webReq.url);

                bool forcedRetry = false;
                string message = string.Format("Web request {0} failed with error '{1}', retrying ({2}/{3})...", webReq.url, webReq.error, m_Retries, m_Options.RetryCount);
#if ENABLE_CACHING
                if (!string.IsNullOrEmpty(m_Options.Hash))
                {
                    CachedAssetBundle cab = new CachedAssetBundle(m_Options.BundleName, Hash128.Parse(m_Options.Hash));
                    if (Caching.IsVersionCached(cab))
                    {
                        message = string.Format("Web request {0} failed to load from cache with error '{1}'. The cached AssetBundle will be cleared from the cache and re-downloaded. Retrying...", webReq.url, webReq.error);
                        Caching.ClearCachedVersion(cab.name, cab.hash);
                        if (m_Options.RetryCount == 0 && m_Retries == 0)
                        {
                            Debug.LogFormat(message);
                            BeginOperation();
                            m_Retries++; //Will prevent us from entering an infinite loop of retrying if retry count is 0
                            forcedRetry = true;
                        }
                    }
                }
#endif
                if (!forcedRetry)
                {
                    if (m_Retries < m_Options.RetryCount)
                    {
                        Debug.LogFormat(message);
                        BeginOperation();
                        m_Retries++;
                    }
                    else
                    {
                        var exception = new Exception(string.Format(
                            "RemoteAssetBundleProvider unable to load from url {0}, result='{1}'.", webReq.url,
                            webReq.error));
                        m_ProvideHandle.Complete<AssetBundleResource>(null, false, exception);
                    }
                }
            }
            webReq.Dispose();
        }

        /// <summary>
        /// Unloads all resources associated with this asset bundle.
        /// </summary>
        public void Unload()
        {
            if (m_AssetBundle != null)
            {
                m_AssetBundle.Unload(true);
                m_AssetBundle = null;
            }
            if (m_downloadHandler != null)
            {
                m_downloadHandler.Dispose();
                m_downloadHandler = null;
            }
            m_RequestOperation = null;
        }
    }

    /// <summary>
    /// IResourceProvider for asset bundles.  Loads bundles via UnityWebRequestAssetBundle API if the internalId starts with "http".  If not, it will load the bundle via AssetBundle.LoadFromFileAsync.
    /// </summary>
    [DisplayName("Stone AssetBundleProvider")]
    public class SAssetBundleProvider : ResourceProviderBase
    {
        /// <inheritdoc/>
        public override void Provide(ProvideHandle providerInterface)
        {
            new AssetBundleResource().Start(providerInterface);
        }

        /// <inheritdoc/>
        public override Type GetDefaultType(IResourceLocation location)
        {
            return typeof(IAssetBundleResource);
        }

        /// <summary>
        /// Releases the asset bundle via AssetBundle.Unload(true).
        /// </summary>
        /// <param name="location">The location of the asset to release</param>
        /// <param name="asset">The asset in question</param>
        public override void Release(IResourceLocation location, object asset)
        {
            if (location == null)
                throw new ArgumentNullException("location");
            if (asset == null)
            {
                Debug.LogWarningFormat("Releasing null asset bundle from location {0}.  This is an indication that the bundle failed to load.", location);
                return;
            }
            var bundle = asset as AssetBundleResource;
            if (bundle != null)
            {
                bundle.Unload();
                return;
            }
        }
    }
}

